package net.openl10n.packaging;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.openl10n.opc4j.Package;
import net.openl10n.opc4j.PackagePart;
import net.openl10n.opc4j.PackagePartName;
import net.openl10n.opc4j.PackageRelationship;
import net.openl10n.opc4j.PackageRelationshipCollection;
import net.openl10n.opc4j.PackagingURIHelper;
import net.openl10n.opc4j.RelationshipSource;
import net.openl10n.opc4j.TargetMode;
import net.openl10n.opc4j.exceptions.InvalidFormatException;
import net.openl10n.opc4j.exceptions.OPC4JException;

import com.google.common.collect.ImmutableList;

public abstract class OpenLocPartContainer extends AnnotationSourceImpl implements RelationshipSource{

	private final RelationshipSource relationshipSource;
	private final List<IdPartPair> parts = new ArrayList<IdPartPair>();
	
	private final OpenLocPartFactoryManager partFactoryManager;
	private final Package pack;
	
	protected OpenLocPartContainer(Package pack, OpenLocPartFactoryManager partFactoryManager){
		this.relationshipSource = pack;
		this.pack = pack;
		this.partFactoryManager = partFactoryManager;
		try{
			initialize();
		}
		catch(OPC4JException e) {
			throw new RuntimeException(e);
		}
	}
	
	protected OpenLocPartContainer(PackagePart part, OpenLocPartFactoryManager partFactoryManager){
		this.pack = part.getPackage();
		this.relationshipSource = part;
		this.partFactoryManager = partFactoryManager;
		try{
			initialize();
		}
		catch(OPC4JException e) {
			throw new RuntimeException(e);
		}
	}

	public Map<URI, OpenLocPart> partMap = new HashMap<URI, OpenLocPart>();

	private void initialize() throws InvalidFormatException, OPC4JException{
		parts.clear();
		PackageRelationshipCollection relationships = getRelationships();
		for(PackageRelationship rel : relationships){
			boolean foundMatch = false;
			for(PackagePart part : pack.getParts()){
				if(rel.getTargetURI().equals(part.getPartName().getURI())){
					OpenLocPart p = partMap.get(rel.getTargetURI());
					if(p == null){
						p = partFactoryManager.create(this, part);
						partMap.put(rel.getTargetURI(), p);
					}
					IdPartPair pair = new IdPartPair(rel.getId(), p);
					parts.add(pair);
					foundMatch = true;
					continue;
				}
			}
			if(!foundMatch){
				// TODO: throw exception?
			}
		}
		
	}
	
	public ImmutableList<IdPartPair> getParts() throws InvalidFormatException, OPC4JException {
		return ImmutableList.copyOf(parts);
	}
	
	private void addPartsInternal(String rId, URI uri, OpenLocPart part){
		parts.add(new IdPartPair(rId, part));
		partMap.put(uri, part);
		
	}
	private URI getURI(){
		if(isPackage())
			return PackagingURIHelper.PACKAGE_ROOT_URI;
		return ((PackagePart)relationshipSource).getPartName().getURI();
	}
	
	private PackagePart getPart(){
		return isPackage() ? null : (PackagePart) relationshipSource;
	}
	
	private boolean isPackage(){
		return relationshipSource == pack;
	}
	
	private PackagePartName createPartName(String partName) throws InvalidFormatException{
		return isPackage() ?
			PackagingURIHelper.createPartName(partName) : 
				PackagingURIHelper.createPartName(
						PackagingURIHelper.combine(
								PackagingURIHelper.getPath(getURI()),
								PackagingURIHelper.getURIFromPath(partName)));
	}
	
	private <T extends OpenLocPart> PackagePartName createPartName(OpenLocPartFactory<T> factory, Class<T> clz) throws InvalidFormatException{
		String partNameStr = "";
		int count = getPartCountOfType(clz) +1;
		if( (factory.hasNameVariable() && count != 1) || factory.isPathNameVariable() ){
			partNameStr = factory.getPartName(partFactoryManager.getPrefix(factory.getContentType()),count);
		}
		else{
			partNameStr = factory.getPartName(partFactoryManager.getPrefix(factory.getContentType()));
		}
		
		return createPartName(partNameStr);
	}
	
	public ExtendedPart addExtendedPart(String relationshipType, String contentType,String targetExt){
		try{
			String prefix = partFactoryManager.getPrefix(contentType);
			int count = pack.getPartsByContentType(contentType).size() +1;
			PackagePartName name = PackagingURIHelper.createPartName(
				PackagingURIHelper.combine(
					PackagingURIHelper.getPath(getURI()), 
					PackagingURIHelper.getURIFromPath(prefix+"udata/data"+(count==1 ? "":count)+targetExt))	
				);
			PackagePart part = pack.createPart(name, contentType);
			PackageRelationship rel = addRelationship(name, TargetMode.INTERNAL, relationshipType);
			ExtendedPart exPart = partFactoryManager.create(ExtendedPart.class, this, part);
			addPartsInternal(rel.getId(), name.getURI(), exPart);
			return exPart;
		}
		catch(OPC4JException e){
			throw new RuntimeException(e);
		}
	}
	
	public ExtendedPart addExtendedPart(String relationshipType, String contentType,String targetExt, String rid){
		try{
			String prefix = partFactoryManager.getPrefix(contentType);
			int count = pack.getPartsByContentType(contentType).size() +1;
			PackagePartName name = PackagingURIHelper.createPartName(
				PackagingURIHelper.combine(
					PackagingURIHelper.getPath(getURI()), 
					PackagingURIHelper.getURIFromPath(prefix+"udata/data"+(count==1 ? "":count)+targetExt))	
				);
			PackagePart part = pack.createPart(name, contentType);
			PackageRelationship rel = addRelationship(name, TargetMode.INTERNAL, relationshipType,rid);
			ExtendedPart exPart = partFactoryManager.create(ExtendedPart.class, this, part);
			addPartsInternal(rel.getId(), name.getURI(), exPart);
			return exPart;
		}
		catch(OPC4JException e){
			throw new RuntimeException(e);
		}
	}
	
	public <T extends OpenLocPart> T addPart(T part){
		return part;
	}
	
	public <T extends OpenLocPart> T addPart(T part, String id){
		return part;
	}

	public <T extends OpenLocPart> T addNewPart(T part){
		return part;
	}
	
	public <T extends OpenLocPart> T addNewPart(T part, String id) throws InvalidFormatException{
		OpenLocPartFactory<T> factory = (OpenLocPartFactory<T>) partFactoryManager.getX(part.getClass());

		PackagePartName partName = PackagingURIHelper.createPartName(factory.getRelationshipType());
		PackagePart packPart = pack.createPart(partName, part.getContentType());
		T openLocPart = partFactoryManager.create(factory.getTargetClass(), this, packPart);
		
		// add relationships etc...
		
		return openLocPart;
	}

	public <T extends OpenLocPart> T addNewPart(Class<T> clz) throws InvalidFormatException{
		OpenLocPartFactory<T> factory = partFactoryManager.get(clz);

		PackagePartName partName = createPartName(factory, clz);
		
		
		PackagePart part = pack.createPart(partName, factory.getContentType());
		T openLocPart = partFactoryManager.create(clz, this, part);

		PackageRelationship rel = addRelationship(partName, TargetMode.INTERNAL, factory.getRelationshipType());
		
		addPartsInternal(rel.getId(), partName.getURI(), openLocPart);
		return openLocPart;
	}
	
	public <T extends OpenLocPart> T addNewPart(Class<T> clz, String contentType) throws InvalidFormatException{
		OpenLocPartFactory<T> factory = partFactoryManager.get(clz);

		PackagePartName partName = createPartName(factory, clz);

		PackagePart part = pack.createPart(partName, contentType);
		T openLocPart = partFactoryManager.create(clz, this, part);

		addRelationship(partName, TargetMode.INTERNAL, factory.getRelationshipType());
		
		return openLocPart;
	}
	
	public <T extends OpenLocPart> T addNewPart(Class<T> clz, String contentType, String id) throws InvalidFormatException{
		OpenLocPartFactory<T> factory = partFactoryManager.get(clz);

		PackagePartName partName = createPartName(factory, clz);

		PackagePart part = pack.createPart(partName, contentType);
		T openLocPart = partFactoryManager.create(clz, this, part);

		addRelationship(partName, TargetMode.INTERNAL, factory.getRelationshipType(), id);
		
		return openLocPart;
	}
	
	public boolean deletePart(OpenLocPart part){
		boolean removed = false;
		pack.deletePart(part.getPartName());
		Iterator<IdPartPair> pIt = parts.iterator();
		while(pIt.hasNext()){
			IdPartPair current = pIt.next();
			if(current.getPart() == part){
				pIt.remove();
				removed = true;
				break;
			}
		}
		partMap.remove(part.getPartName().getURI());
		return removed;
	}
	
	public boolean deletePart(String id){
		Iterator<IdPartPair> pIt = parts.iterator();
		while(pIt.hasNext()){
			IdPartPair current = pIt.next();
			if(current.getRelationshipId().equals(id)){ 
				return deletePart(current.getPart());
			}
		}
		return false;
	}

	public void deleteParts(Iterable<OpenLocPart> parts){
		for(OpenLocPart part : parts){
			deletePart(part);
		}
	}

	public String getIdOfPart(OpenLocPart part){
		for(IdPartPair idp: parts){
			if(idp.getPart() == part)
				return idp.getRelationshipId();
		}
		return null;
	}
	
	public OpenLocPart getPartById(String id){
		for(IdPartPair idp: parts){
			if(idp.getRelationshipId().equals(id))
				return idp.getPart();
		}
		return null;
	}
	
	public <T extends OpenLocPart> int getPartCountOfType(Class<T> clz){
		int count = 0;
		for(IdPartPair idp: parts){
			if(idp.getPart().getClass() == clz)
				count++;
		}
		return count;
	}
	
	public <T extends OpenLocPart> ImmutableList<T> getPartsOfType(Class<T> clz){
		List<T> retList = new ArrayList<T>();
		for(IdPartPair idp: parts){
			if(idp.getPart().getClass() == clz)
				retList.add(clz.cast(idp.getPart()));
		}
		return ImmutableList.copyOf(retList);
	}

	public <T extends OpenLocPart> T getPartOfType(Class<T> clz){
		for(IdPartPair idp: parts){
			if(idp.getPart().getClass() == clz)
				return clz.cast(idp.getPart());
		}
		return null;
	}

	
	public <T extends OpenLocPart> void getPartsOfType(Class<T> clz, Collection<T> collection){
		for(IdPartPair idp: parts){
			if(idp.getPart().getClass() == clz)
				collection.add(clz.cast(idp.getPart()));
		}
	}

	
	@Override
	public PackageRelationship addExternalRelationship(String target,
			String relationshipType) {
		return relationshipSource.addExternalRelationship(target, relationshipType);
	}

	@Override
	public PackageRelationship addExternalRelationship(String target,
			String relationshipType, String id) {
		return relationshipSource.addExternalRelationship(target, relationshipType, id);
	}

	@Override
	public PackageRelationship addRelationship(PackagePartName targetPartName,
			TargetMode targetMode, String relationshipType) {
		return relationshipSource.addRelationship(targetPartName, targetMode, relationshipType);
	}

	@Override
	public PackageRelationship addRelationship(PackagePartName targetPartName,
			TargetMode targetMode, String relationshipType, String id) {
		return relationshipSource.addRelationship(targetPartName, targetMode, relationshipType, id);
	}

	@Override
	public void clearRelationships() {
		relationshipSource.clearRelationships();
		
	}

	@Override
	public PackageRelationship getRelationship(String id) {
		return relationshipSource.getRelationship(id);
	}

	@Override
	public PackageRelationshipCollection getRelationships()
			throws InvalidFormatException, OPC4JException {
		return relationshipSource.getRelationships();
	}

	public ImmutableList<PackageRelationship> getExternalRelationships() throws InvalidFormatException, OPC4JException{
		ImmutableList.Builder<PackageRelationship> builder = ImmutableList.builder();
		for(PackageRelationship rel : getRelationships()){
			if(rel.getTargetMode() == TargetMode.EXTERNAL)
				builder.add(rel);
		}
		return builder.build();
	}
	
	public void deleteExternalRelationship(PackageRelationship rel){
		if(rel.getTargetMode() == TargetMode.EXTERNAL){
			pack.removeRelationship(rel.getId());
		}
	}
	
	
	@Override
	public PackageRelationshipCollection getRelationshipsByType(
			String relationshipType) throws InvalidFormatException,
			IllegalArgumentException, OPC4JException {
		return getRelationshipsByType(relationshipType);
	}

	@Override
	public boolean hasRelationships() {
		return relationshipSource.hasRelationships();
	}

	@Override
	public boolean isRelationshipExists(PackageRelationship rel) {
		return relationshipSource.isRelationshipExists(rel);
	}

	@Override
	public void removeRelationship(String id) {
		relationshipSource.removeRelationship(id);
	}
	
	protected OpenLocPartFactoryManager getPartFactoryManager() {
		return partFactoryManager;
	}
	
	protected void onClose(){
		try{
			for(IdPartPair pId : getParts()){
				pId.getPart().onClose();
			}
		}
		catch(OPC4JException e){
			throw new RuntimeException(e);
			// TODO handle exception
		}
		
	}

}
